#include "d3d.h"
#include "d3d_util.h"
#include "..\engine\error.h"
#include "..\engine\engine.h"
#include "..\engine\memorymanager.h"
#include "..\common\types.h"
#include "..\common\common.h"
#include "..\common\def.h"

D3DVERTEXELEMENT9 VertexDecl[] =
{
	{0,0,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,0},
	{0,sizeof(float) * 3,D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,0},
	D3DDECL_END()
};

/**************************************
 * General utility functions
 **************************************/
D3DXVECTOR3 VectorToD3DVector3(const Vector3f& vec)
{
	return D3DXVECTOR3((float*)&vec);
}

D3DXVECTOR4 VectorToD3DVector4(const Vector3f& vec)
{
	return D3DXVECTOR4((float*)&vec);
}

Vector3f D3DVector3ToVector(const D3DXVECTOR3& vec)
{
	return Vector3f((float*)&vec);
}

D3DXVECTOR3 ColorToD3DVector3(const Color3f& col)
{
	return D3DXVECTOR3((float*)&col);
}

D3DXVECTOR4 ColorToD3DVector4(const Color3f& col)
{
	return D3DXVECTOR4(col.r, col.g, col.b, 0.0f);
}

DWORD ColorToD3DColor(const Color3f& col)
{
	return D3DCOLOR_COLORVALUE(col.r, col.g, col.b, 0.0f);
}

D3DXMATRIX GetProjectionTransform(float Near, float Far, float fov, float aspect)
{
	D3DXMATRIX m;
	SETONEIFZERO(aspect);
	
	D3DXMatrixPerspectiveFovLH(&m, fov, aspect, Near, Far); 
	return m;
}

void GetVertexCloudMetrics(VertexComplex* pVertices, unsigned int vertexCount, Vector3f* pCenter, float* pRadius)
{
	if(pVertices)
	{
		Vector3f sumPos(0,0,0), minPos(pVertices[0].position), maxPos(pVertices[0].position);
		for(int i = 0; i < vertexCount; i++)
		{
			Vector3f p = Vector3f( pVertices[i].position );
			sumPos += p;
			Vector3f::maxVec(&maxPos, &maxPos, &p);
			Vector3f::minVec(&minPos, &minPos, &p);
		}

		if(pRadius)
			*pRadius = Vector3f::distance(&minPos, &maxPos) / 2.0f;

		if(vertexCount > 0 && pCenter)
			*pCenter = (sumPos / vertexCount);
	}
}

/**************************************
 * MeshPartData definition
 **************************************/	
MeshPartData::MeshPartData()
{
	opacity = 0.0f;
	m_pShader = NULL; 
	m_pVertices = NULL; 
	m_pIndicies = NULL; 
	vertexCount = 0; 
	triangleCount = 0; 
	frameCount = 0; 
}

/**************************************
 * FogData definition
 **************************************/	
FogData::FogData()
{
	m_Start = 0.0f;
	m_End = 1000.0f;
	m_FogPlane = D3DXVECTOR3(0.0f,0.0f,1.0f);
	
	m_StartColor = D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f);
	m_EndColor = D3DXVECTOR4(1.0f,1.0f,1.0f,1.0f);
}

/**************************************
 * Sun data definition
 **************************************/
SunData::SunData()
{
	m_IsActive = true;
	for(uint i = 0; i < 4; i++)
		m_pFlares[i] = NULL;
	m_Position = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	m_Direction = D3DXVECTOR3(0.0f, -1.0f, 0.0f);
	m_Diffuse =  D3DXVECTOR3(0.5f, 0.5f, 0.2f);
	m_Ambience = D3DXVECTOR3(0.1f, 0.1f, 0.1f);
}; 	

/**************************************
 * Releases sun flare and halo graphics
 * used for lens simulation effects
 **************************************/
HRESULT SunData::ReleaseFlares()
{
	HRESULT hr = S_OK;

	for(uint i = 0; i < 4; i++) {
		if(m_pFlares[i]) {
			if(FAILED(m_pFlares[i]->Release()))
				hr = E_FAIL;
			else
				m_pFlares[i] = NULL;
		}
	}
	return hr;
}
/**************************************
 * Loads the sun flare and halo graphics
 * for lens simulation effects
 **************************************/
HRESULT SunData::CreateFlares(const string& flare,const string& haloA,const string& haloB,const string& haloC)
{
	D3DImage* pFlares[4] = {NULL};
	HRESULT hr;
	do {

		CD3D9Device* pRenderEngine = CEngine::GetInstance()->GetRenderer();
		
		if(FAILED(pRenderEngine->CreateSprite(&pFlares[0], flare)) ||
			FAILED(pRenderEngine->CreateSprite(&pFlares[1], haloA)) ||
			FAILED(pRenderEngine->CreateSprite(&pFlares[2], haloB)) ||
			FAILED(pRenderEngine->CreateSprite(&pFlares[3], haloC)))
		{
			EE_LOGERROR("Failed to create sun flare.");
			hr = E_FAIL;
			break;
		}

		if(FAILED(ReleaseFlares()))
		{
			EE_LOGERROR("Failed to release previous sun flare data.");
			hr = E_FAIL;
			break;
		}

		for(uint i = 0; i < 4; i++)
			m_pFlares[i] = pFlares[i];
	
		return S_OK;
	}while(false);

	for(uint i = 0; i < 4; i++) {
		if(pFlares[i]) {
			pFlares[i]->Release();
			pFlares[i] = NULL;
		}
	}
	return E_FAIL;
}; 	

/**************************************
 * ViewOrrientation definition
 **************************************/	
ViewOrrientation::ViewOrrientation()
{
	D3DXMatrixIdentity(&m_View); 
	D3DXMatrixIdentity(&m_Projection); 
}

/**************************************
 * Returns true is specified bounds sphere
 * is contained or intersects the specified
 * view frustrum
 **************************************/	
bool ViewOrrientation::ContainsSphere(ViewOrrientation* view, Spheref* pBoundsSphere)
{
	const Vector3f *pV = &view->m_Position;
	const Vector3f *pC = &pBoundsSphere->m_center;
	const float radius = pBoundsSphere->m_radius;

	const Vector3f offset(pV->x - pC->x, pV->y - pC->y, pV->z - pC->z);

	if(Vector3f::dot(&offset, &offset) > (radius * radius))	{	
		
		for(uint i = 0; i < 6; i++) {
			if(Planef::ABOVE == Planef::ClassifySphere(&view->m_frustrumPlanes[i], pBoundsSphere))
				return false;
		}
	}
	return true; 
}

/**************************************
 * Returns true is specified points are
 * contained within the specified view
 * frustrum
 **************************************/	
bool ViewOrrientation::ContainsPoints(ViewOrrientation* view,Vector3f* pPoints, uint count)
{
	Planef *pl; 
	Vector3f *p;
	for(uint j = 0; j < count; j++) {

		p = &pPoints[j];

		for(uint i = 0;  i < 6 ; i++) {
			pl = &view->m_frustrumPlanes[i]; 
			if(0.0f > pl->normal.x * p->x + pl->normal.y * p->y + pl->normal.z * p->z + pl->distance)
				return false; 
		}
	}
	return true; 

}

/**************************************
 * Returns true is specified point is
 * contained within the specified view
 * frustrum
 **************************************/	
bool ViewOrrientation::ContainsPoint(ViewOrrientation* view, const Vector3f& point)
{
	Planef *pl; 
	for(uint i = 0;  i < 6 ; i++) {
		pl = &view->m_frustrumPlanes[i]; 
		if(0.0f > pl->normal.x * point.x + pl->normal.y * point.y + pl->normal.z * point.z + pl->distance)
			return false; 
	}

	return true; 
}


/**************************************
 * IResource definition
 **************************************/
IResource::IResource(CD3D9Device* const pDevice) : m_pD3DDevice(pDevice)
{	
	m_IsValid = true; 
}

/**************************************
 * IResource definition
 **************************************/
bool IResource::IsValid()
{	
	return m_IsValid; 
}

string IResource::Identity()
{
	return m_Identity; 
}
CD3D9Device* IResource::GetDevice() const
{
	return m_pD3DDevice; 
}

/**************************************
 * D3DRenderable definition
 **************************************/
D3DRenderable::D3DRenderable(CD3D9Device* const pDevice) : IResource(pDevice)
{	
	pIndexBuffer = NULL; 
	pVertexBuffer = NULL;
	
	m_Radius = 0.0f;
	m_ElementSize = 0;
	m_RenderFlags = 0;
	m_NumPrimitives = 0; 
	m_pTextureUnitA = NULL; 
	m_pVertices = NULL;
	m_pIndicies = NULL; 
	m_pShader = NULL; 
	m_IsInstance = false;
}

void D3DRenderable::setShader(D3DShader* pShader)
{
	m_pShader = pShader; 
}

void D3DRenderable::setVelocity(const Vector3f& velocity)
{
	m_Velocity = velocity;
}

Texture* D3DRenderable::getTexture(const unsigned int idx)
{
	return (idx == 0) ? m_pTextureUnitA : NULL;
}
D3DShader* D3DRenderable::getShader()
{
	return m_pShader;
}
unsigned int D3DRenderable::getVertexCount() const
{
	return m_NumPrimitives * 3;
}
void* D3DRenderable::getVertices()
{
	return m_pVertices;
}
unsigned short* D3DRenderable::getIndicies()
{
	return m_pIndicies;
}

unsigned int D3DRenderable::getRenderFlags() const
{
	return m_RenderFlags;
}
Texture* D3DRenderable::getMaskTexture()
{
	return NULL;
}
float D3DRenderable::GetRadius() const
{
	return m_Radius;
}
Vector3f D3DRenderable::getVelocity() const
{
	return m_Velocity;
}
HRESULT D3DRenderable::PreRender()
{	
	return S_OK;
}

HRESULT D3DRenderable::PostRender()
{	
	return S_OK;
}

void D3DRenderable::setRenderFlags(const unsigned int flags)
{
	m_RenderFlags = flags;
}

/**************************************
 *Sends rendering data to this mesh to
 *request its Drawing
 **************************************/
void D3DRenderable::Draw(const Matrix4f &transform, D3DShader* const pShader)
{
	if(pShader)
		pShader->AddRenderQue(this, transform); 
}



/**************************************
 * RenderTarget definition
 **************************************/
RenderTarget::RenderTarget(CD3D9Device* const pDevice) : IResource(pDevice)
{
	m_Identity = "RenderTarget"; 
	m_pTargetSurface = NULL; 
	m_pSurface = NULL; 
	m_pTargetTexture = NULL; 
	m_ClearColor = D3DCOLOR_COLORVALUE(0.0f,0.0f,0.0f,1.0f); 
}

string RenderTarget::GetType() const
{
	return string("RenderTarget"); 
}

HRESULT RenderTarget::Release()
	{
	m_IsValid = false; 
		if(m_pSurface)
		{
			m_pSurface->Release(); 
			m_pSurface = NULL; 
		}

		if(m_pTargetSurface)
		{
			m_pTargetSurface->Release(); 
			m_pTargetSurface = NULL; 
		}
		
		if(m_pTargetTexture)
		{
			m_pTargetTexture->Release(); 
			m_pTargetTexture = NULL; 
		}
		return S_OK; 
	}

HRESULT RenderTarget::Reload()
{
	return S_OK; 
}

/**************************************
 *Renders the screen quad geometry
 **************************************/
void CD3D9Device::RenderScreenGeometry()
{
	m_pD3DDevice->SetVertexDeclaration(m_pVertexDeclaration); 
	m_pD3DDevice->SetStreamSource(0, m_pScreenQuadVB, 0, Vertex::size); 
	m_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2); 
}

/**************************************
 *Creates the screen quad geometry
 **************************************/
HRESULT CD3D9Device::CreateScreenGeometry()
{

	void *m_pVertices		= NULL; 

	try{
		

		if(FAILED(m_pD3DDevice->CreateVertexDeclaration(VertexDecl, &m_pVertexDeclaration)))
			throw EE_EXCEPTION("CreateVertexDeclaration(..) failed."); 

		//If a screen quad already exsists return ok. This was the only way it could
		//have been created
		if(m_pScreenQuadVB)
			return S_OK; 

		//Create the vb first to prevent additional work in the case of failure
		if(m_pD3DDevice->CreateVertexBuffer(Vertex::size * 6, D3DUSAGE_WRITEONLY, 0,
			D3DPOOL_DEFAULT, &m_pScreenQuadVB,NULL) != D3D_OK)
			throw EE_EXCEPTION( "Could not Create screen geometry vertex buffer."); 

		Vertex pVertexData[6] = {0};

		pVertexData[0].position[0] = -1.0f; 
		pVertexData[0].position[1] = -1.0f;
		pVertexData[0].texCoord[0] = 0.0f; 
		pVertexData[0].texCoord[1] = 1.0f; 
		
		pVertexData[1].position[0] = -1.0f; 
		pVertexData[1].position[1] = 1.0f; 
		pVertexData[1].texCoord[0] = 0.0f; 
		pVertexData[1].texCoord[1] = 0.0f; 

		
		pVertexData[2].position[0] = 1.0f; 
		pVertexData[2].position[1] = 1.0f; 
		pVertexData[2].texCoord[0] = 1.0f; 
		pVertexData[2].texCoord[1] = 0.0f; 

		pVertexData[3].position[0] = 1.0f; 
		pVertexData[3].position[1] = -1.0f;
		pVertexData[3].texCoord[0] = 1.0f; 
		pVertexData[3].texCoord[1] = 1.0f; 


		pVertexData[4].position[0] = -1.0f; 
		pVertexData[4].position[1] = -1.0f;
		pVertexData[4].texCoord[0] = 0.0f; 
		pVertexData[4].texCoord[1] = 1.0f; 

		pVertexData[5].position[0] = 1.0f; 
		pVertexData[5].position[1] = 1.0f; 
		pVertexData[5].texCoord[0] = 1.0f; 
		pVertexData[5].texCoord[1] = 0.0f; 

		//Attempt to pass the data to the vertex buffer
		if(m_pScreenQuadVB->Lock(0, Vertex::size * 6, (void **) &m_pVertices, 0) != D3D_OK)
			throw EE_EXCEPTION( "Could not lock screen quad vertex buffer."); 

		memcpy(m_pVertices, pVertexData, Vertex::size * 6); 
		
		if(m_pScreenQuadVB->Unlock() != D3D_OK)
			throw EE_EXCEPTION( "Could not unlock screen quad vertex buffer."); 

		return S_OK; 
	}
	catch(CError::CErrorData errorData)
	{
		CError::LogError(errorData.mpMessage); 
		m_pVertices = NULL; 

		if(m_pScreenQuadVB)
		{
			m_pScreenQuadVB->Release(); 
			m_pScreenQuadVB = NULL; 
		}

		return E_FAIL; 
	}
}

/**************************************
 *Releases the screen quad geometry
 **************************************/
HRESULT CD3D9Device::ReleaseScreenGeometry()
{
	if(m_pScreenQuadVB)
	{
		m_pScreenQuadVB->Release();
		m_pScreenQuadVB = NULL;
	}
	return S_OK;
}